/*
 *  Copyright 2001-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package ar.uba.fi.posgrado.common.dao.impl;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.criterion.Expression;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import ar.uba.fi.posgrado.common.dao.DAOException;
import ar.uba.fi.posgrado.common.dao.NoSuchEntityException;
import ar.uba.fi.posgrado.common.dao.UserDAO;
import ar.uba.fi.posgrado.common.exception.InvalidArgumentException;
import ar.uba.fi.posgrado.common.exception.MissingArgumentException;
import ar.uba.fi.posgrado.common.model.Role;
import ar.uba.fi.posgrado.common.model.User;

/**
 * Dao concreto para {@link User user}.<br>
 * 
 * @author nicolas.gonzalez
 * @see UserDAO
 */
public class UserDAOImpl extends HibernateDaoSupport implements UserDAO {

    /** Logger */
    private static Logger logger = Logger.getLogger(UserDAOImpl.class);
    
    /**
     * @see ar.uba.fi.posgrado.common.dao.UserDAO#findByName(java.lang.String)
     */
    @SuppressWarnings("unchecked")
    public User findByName(final String userName) throws NoSuchEntityException,
            DAOException {
        User response = null;
        try {
            response = (User)this.getHibernateTemplate().execute(new HibernateCallback() {
                public Object doInHibernate(Session session) {
                    Criteria criteria = session.createCriteria(User.class);
                    criteria.add(Expression.eq("name", userName));
                    return criteria.uniqueResult();
                }
            }
            );
            
            if (response == null) {
                // Necesario ya q el criteria no falla al no encontrar
                throw new NoSuchEntityException("No existe intancia de User" 
                        + ", con userName: " + userName);
            }
        }
        catch (ObjectRetrievalFailureException e) {
            throw new NoSuchEntityException("No existe intancia de User" 
                    + ", con userName: " + userName + " " + e.getMessage(),e);
        }
        catch (DataAccessException e) {
            throw new DAOException("Excepcion al buscar una intancia de User " 
                    + ", con userName: " + userName + ". Mensaje: " + e.getMessage(),e);
        }
        
        return response;        
    }
    
    /**
     * @see ar.uba.fi.posgrado.common.dao.UserDAO#findRolesByName(java.util.List)
     */
    @SuppressWarnings("unchecked")
    public List<Role> findRolesByName(List<String> roleNames)
            throws DAOException {
        List<Role> response = new ArrayList<Role>();

        try {
            for (Iterator<String> iterator = roleNames.iterator(); iterator.hasNext();) {
                String roleName = iterator.next();
    
                Role role = (Role)this.getHibernateTemplate().load(Role.class, roleName);
                response.add(role);
            }
        }
        catch (DataAccessException e) {
            throw new DAOException("Excepcion al buscar los roles correspondientes a: " 
                    + roleNames + ". Mensaje: " + e.getMessage(),e);
        }
        
        return response;
    }

    /**
     * @see ar.uba.fi.posgrado.common.dao.UserDAO#getAvailableRoles(ar.uba.fi.posgrado.common.model.User)
     */
    @SuppressWarnings("unchecked")
    public List<Role> getAvailableRoles(User user)
            throws InvalidArgumentException, MissingArgumentException,
            DAOException {
        if (user == null) {
            throw new MissingArgumentException("El usuario del que se quieren los roles " +
                    " disponibles no puede ser nulo");
        }
        if (StringUtils.isEmpty(user.getName())) {
            throw new InvalidArgumentException("El nombre de usuario no puede ser nulo o vacio");
        }

        /*
         * Se podria hacer de otra manera:
         * Obtener todos los roles.
         * A esos roles Restarle los roles q ya tiene y fin de la historia
         * utilizando para ello: CollectionUtils#subtract(todos, losQueTiene).
         * Lo hice con nativeSQL para q vean como se puede usar esto!
         */
        
        // Se resuelve con NATIVE SQL para que sea mas sencillo
        final StringBuffer nativeQuery = new StringBuffer("select {role.*} from ROL role where role.NOMBRE_ROL not in (").
        append(" select NOMBRE_ROL from USUARIO_ROL where NOMBRE_USUARIO ='").
        append(user.getName()).append("');");

        List<Role> response = null;
        try {
            // Se hace con una inner class para poder usar la NativeSQL. Lo mismo pasaria con un Criteria
            response = this.getHibernateTemplate().executeFind(
                new HibernateCallback() {
                    /** @see org.springframework.orm.hibernate3.HibernateCallback#doInHibernate(org.hibernate.Session) */
                    public Object doInHibernate(Session session)
                            throws HibernateException, SQLException {
                        SQLQuery query =    session.createSQLQuery(nativeQuery.toString()).
                                            addEntity("role", Role.class);
                        return query.list();
                    }
                    
                }
            );
        }
        catch (DataAccessException e) {
            throw new DAOException("Excepcion al buscar los roles disponibles del Usuario." + user +
                                    ". Mensaje: " + e.getMessage(),e);
        }
        
        
        return response;
    }

    /**
     * @see ar.uba.fi.posgrado.common.dao.UserDAO#findAll()
     */
    @SuppressWarnings("unchecked")
    public List<User> findAll() throws DAOException {
        List response = null;
        if (logger.isDebugEnabled()) {
            logger.debug("Buscando todas las instancias de User.");
        }
        try {
            response = this.getHibernateTemplate().loadAll(User.class);
        }
        catch (DataAccessException e) {
            throw new DAOException("Excepcion al buscar las intancias de User." + 
                                    ". Mensaje: " + e.getMessage(),e);
        }
        
        return response;
    }
    
    /**
     * @see ar.uba.fi.posgrado.common.dao.UserDAO#findAllRoles()
     */
    @SuppressWarnings("unchecked")
    public List<Role> findAllRoles() throws DAOException {
        List response = null;
        if (logger.isDebugEnabled()) {
            logger.debug("Buscando todas las instancias de Role.");
        }
        try {
            response = this.getHibernateTemplate().loadAll(Role.class);
        }
        catch (DataAccessException e) {
            throw new DAOException("Excepcion al buscar las intancias de Role." + 
                                    ". Mensaje: " + e.getMessage(),e);
        }
        
        return response;
    }

    /**
     * @see ar.uba.fi.posgrado.common.dao.UserDAO#save(ar.uba.fi.posgrado.common.model.User)
     */
    @SuppressWarnings("unchecked")
    public void save(User user) throws InvalidArgumentException, MissingArgumentException, DAOException {
        if (user == null) {
            throw new MissingArgumentException("El usuario a guardar no puede ser nulo");
        }
        if (StringUtils.isEmpty(user.getName())) {
            throw new InvalidArgumentException("El nombre de usuario no puede ser nulo o vacio");
        }
        
        if (logger.isDebugEnabled()) {
            logger.debug("Guardando una instancia de User. Instancia: " + user);
        }
        
        try {
            this.getHibernateTemplate().save(user);
        }
        catch (DataAccessException e) {
            throw new DAOException("Excepcion al guardar una intancia de User. " + 
                    "Instancia : " + user+ ". Mensaje: " + e.getMessage(),e);
        }
    }

    /**
     * @see ar.uba.fi.posgrado.common.dao.UserDAO#update(ar.uba.fi.posgrado.common.model.User)
     */
    public void update(User user) throws MissingArgumentException, DAOException {
        if (user == null) {
            throw new MissingArgumentException("El usuario a modificar no puede ser nulo");
        }
        if (StringUtils.isEmpty(user.getName())) {
            throw new InvalidArgumentException("El nombre de usuario no puede ser nulo o vacio");
        }
        
        if (logger.isDebugEnabled()) {
            logger.debug("Modificando una instancia de User. Instancia: " + user);
        }
        
        try {
            this.getHibernateTemplate().update(user);
        }
        catch (DataAccessException e) {
            throw new DAOException("Excepcion al modificar una intancia de User. " + 
                    "Instancia : " + user+ ". Mensaje: " + e.getMessage(),e);
        }
    }

    /**
     * @see ar.uba.fi.posgrado.common.dao.UserDAO#remove(ar.uba.fi.posgrado.common.model.User)
     */
    @SuppressWarnings("unchecked")
    public void remove(User user) throws MissingArgumentException, DAOException {
        if (user == null) {
            throw new MissingArgumentException("El usuario a eliminar no puede ser nulo");
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Eliminando una instancia de User. Instancia: " + user + " ...");
        }
        
        try {
            this.getHibernateTemplate().delete(user);
        }
        catch (DataAccessException e) {
            throw new DAOException("Excepcion al eliminar una intancia de Usuario. " + 
                    "Instancia : " + user + ". Mensaje: " + e.getMessage(),e);
        }
    }
    
}
